package org.csu.chatserver.handlerDispatcher;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelMatcher;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.stream.ChunkedFile;
import io.netty.util.concurrent.GlobalEventExecutor;
import org.csu.chatserver.entity.dto.*;
import org.csu.chatserver.entity.pojo.RoomMessage;
import org.csu.chatserver.entity.pojo.SingleMessage;
import org.csu.chatserver.entity.pojo.User;
import org.csu.chatserver.entity.vo.HistoryMsgVO;
import org.csu.chatserver.entity.vo.SingleMsgVO;
import org.csu.chatserver.mapper.UserMapper;
import org.csu.chatserver.msg.MessageData;
import org.csu.chatserver.msg.Payload;
import org.csu.chatserver.msg.Response;
import org.csu.chatserver.service.RoomMessageService;
import org.csu.chatserver.service.SingleMessageService;
import org.csu.chatserver.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Component
@ChannelHandler.Sharable
public class WebSocketHandlerDispatcher extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    @Autowired
    private UserService userService;
    @Autowired
    private SingleMessageService singleMessageService;
    @Autowired
    private RoomMessageService roomMessageService;

    @Autowired
    private UserMapper userMapper;

    public WebSocketHandlerDispatcher() {}

    private final ObjectMapper objectMapper = new ObjectMapper();

    public static final ConcurrentHashMap<String, Long> lastHeartbeatTimeMap = new ConcurrentHashMap<>();

    // 用于单聊
    public static final ConcurrentHashMap<String, Channel> userChannelMap = new ConcurrentHashMap<>();

    // 用于群聊
    private static final ConcurrentHashMap<String, ChannelGroup> roomChannelGroupMap = new ConcurrentHashMap<>();

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) {
        //todo 理清发送与响应流程 String->json->String->制造响应String->JSON->String
        String received  = msg.text();
        ObjectMapper mapper = new ObjectMapper();
        //todo 理清全局异常

        try {
            // 1. 将JSON解析为通用树模型
            JsonNode rootNode = objectMapper.readTree(received);
            // 2. 获取类型字段
            String type = rootNode.get("type").asText();
            if (!type.equals("FILE_UPLOAD")) {
                MessageData data = mapper.readValue(received, MessageData.class);
                processHandle(ctx,data);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
                                                                                    //todo 未添加到方法签名 必须抛出
    private void processHandle(ChannelHandlerContext ctx,MessageData data) throws JsonProcessingException {
        String type = data.getType();
        switch (type) {
            //connect
            case "CONNECT":
                System.out.println("用户连接");
                break;
            case "REGISTER":
                //todo 注册流程
                System.out.println("注册信息 接收成功");
                RegisterDTO registerDTO = objectMapper.convertValue(data.getPayload(), RegisterDTO.class);
                ctx.writeAndFlush(new TextWebSocketFrame(objectMapper.writeValueAsString(new Response<>(userService.register(registerDTO)))));
                break;
            case "LOGIN":{
                //todo 登录流程花间
                System.out.println("登录信息 接收成功");
                System.out.println(data.getPayload());
                LoginDTO loginDTO = objectMapper.convertValue(data.getPayload(), LoginDTO.class);
                String id = userService.login(loginDTO);

                ByteBuf responseBuf = ByteBufAllocator.DEFAULT.heapBuffer();
                try {
                    if (id != null) {
                        //加入channel();
                        userChannelMap.put(id, ctx.channel());
                        //System.out.println("[DEBUG] userChannelMap after put: " + userChannelMap.keySet());

                        // 使用ByteBuf优化响应发送
                        // String json = objectMapper.writeValueAsString(new Response<>("登录成功", id));
                        // responseBuf.writeBytes(json.getBytes(StandardCharsets.UTF_8));
                        responseBuf.writeBytes(objectMapper.writeValueAsBytes(new Response<>("登录成功", id)));
                        ctx.writeAndFlush(new TextWebSocketFrame(responseBuf.retain()));

                        broadcastOnlineUsers();

                        //推送该用户的未读消息统计
                        int userId = Integer.parseInt(id);
                        Map<Integer,Integer> counts = singleMessageService.getUnreadCounts(userId);
                        // 直接用 objectMapper 序列化到 ByteBuf
                        ByteBuf unreadBuf = ByteBufAllocator.DEFAULT.heapBuffer();
                        unreadBuf.writeBytes(objectMapper.writeValueAsBytes(
                                new Response<>("UNREAD_COUNTS", counts)
                        ));
                        ctx.writeAndFlush(new TextWebSocketFrame(unreadBuf));
                    }else{
                        // 错误响应同样使用ByteBuf优化
                        // String errorJson = objectMapper.writeValueAsString(new Response<>("用户名或密码错误"));
                        responseBuf.writeBytes(objectMapper.writeValueAsBytes(new Response<>("用户名或密码错误")));
                        ctx.writeAndFlush(new TextWebSocketFrame(responseBuf.retain()));
                    }
                } catch (JsonProcessingException e) {
                    responseBuf.release();
                    //ctx.close();
                    e.printStackTrace();
                } finally {
                    responseBuf.release();
                }
                break;}
            //heart beat
            case "HEARTBEAT":
                String userId = getUserIdByChannel(ctx.channel());  // ✅ 获取 userId
                System.out.println("收到心跳包 from client");

                ByteBuf heartbeatBuf = ByteBufAllocator.DEFAULT.heapBuffer();
                try {
                    if (userId != null) {
                        lastHeartbeatTimeMap.put(userId, System.currentTimeMillis());
                        System.out.println("[心跳] 收到用户 " + userId + " 的心跳包");

                        // 可选：返回 ack
                        String ackJson = objectMapper.writeValueAsString(
                                new Response<>("HEARTBEAT_ACK", "ok")
                        );
                        heartbeatBuf.writeBytes(ackJson.getBytes(StandardCharsets.UTF_8));
                        ctx.writeAndFlush(new TextWebSocketFrame(heartbeatBuf.retain()));
                    } else {
                        System.out.println("[心跳] 收到未知 Channel 的心跳包（未登录用户）");
                    }
                } catch (JsonProcessingException e) {
                    heartbeatBuf.release();
//                    ctx.close();
                    e.printStackTrace();
                } finally {
                    heartbeatBuf.release();
                }
                ctx.writeAndFlush(new PongWebSocketFrame());
                break;
            case "REQUEST_ONLINE_USERS": {
                // 复用上面的方法拿到在线用户列表
                List<Map<String, String>> onlineUserInfos = getOnlineUserInfos();
                // 再打包成 JSON 发给当前这个 channel
                String json = objectMapper.writeValueAsString(
                        new Response<>("ONLINE_USERS", onlineUserInfos)
                );
                System.out.println("111"+onlineUserInfos);
                ctx.channel().writeAndFlush(new TextWebSocketFrame(json));
                break;
            }
            //single_chat
            case "GET_USER_LIST":
                System.out.println("GET_USER_LIST");
                ctx.writeAndFlush(new TextWebSocketFrame(objectMapper.writeValueAsString(new Response<>("GET_USER_LIST_ACK",userService.getUserList()))));
                break;
            case "SINGLE_MESSAGE":{
                //单发信息
                SingleMsgDTO singleMsgDTO = objectMapper.convertValue(data.getPayload(), SingleMsgDTO.class);
                int senderId = singleMsgDTO.getSenderId(); // 找到发送者用户ID
                String targetId= String.valueOf(singleMsgDTO.getTargetId()); // 找到发送者用户ID
                String content= singleMsgDTO.getContent(); // 找到发送者用户ID
                String Mtype = singleMsgDTO.getType(); // 找到发送者用户ID

                // 由俩个不同的buffer处理 一个是ack 一个是消息
                ByteBuf ackBuf  = ByteBufAllocator.DEFAULT.heapBuffer();
                ByteBuf targetMsgBuf = ByteBufAllocator.DEFAULT.heapBuffer();
                try {
                    //todo 在这里鉴权 鉴权由Map实现才对
                    if (senderId != 0) {
                        String errorJson = objectMapper.writeValueAsString(new Response<>("请先登录"));
                        ackBuf.writeBytes(errorJson.getBytes(StandardCharsets.UTF_8));
                        ctx.writeAndFlush(new TextWebSocketFrame(ackBuf.retain()));
                    }

                    //todo 返回消息类 离线消息
                    Channel targetChannel = userChannelMap.get(targetId); // 查找目标 Channel
                    if (targetChannel != null && targetChannel.isActive()) {

                        SingleMsgVO singleMsgVO = new SingleMsgVO();
                        singleMsgVO.setSenderId(senderId);
                        singleMsgVO.setTargetId(Integer.parseInt(targetId));
                        singleMsgVO.setContent(content);
                        singleMsgVO.setType(Mtype);
                        //todo 返回消息类 离线消息
                        if (targetChannel != null && targetChannel.isActive()) {
                            // 目标用户的消息
                            Response<SingleMsgVO> messageResponse = new Response<>("SINGLE_MESSAGE", singleMsgVO); // 类型 + 内容
                            String jsonMessage = objectMapper.writeValueAsString(messageResponse);
                            targetMsgBuf.writeBytes(jsonMessage.getBytes(StandardCharsets.UTF_8));
                            targetChannel.writeAndFlush(new TextWebSocketFrame(targetMsgBuf.retain()));

                            // 发送者的确认
                            String ackJson = objectMapper.writeValueAsString(new Response<>("私发消息已经发送"));
                            ackBuf.writeBytes(ackJson.getBytes(StandardCharsets.UTF_8));
                            ctx.writeAndFlush(new TextWebSocketFrame(ackBuf.retain()));
                            System.out.println("用户 " + senderId + " 发送单聊消息给 " + targetId);
                            ctx.writeAndFlush(new TextWebSocketFrame(ackBuf.retain()));
                        }
                    }
                }catch (JsonProcessingException e) {
                    ackBuf.release();
                    targetMsgBuf.release();
//                    ctx.close();
                    e.printStackTrace();
                } finally {
                    ackBuf.release();
                    targetMsgBuf.release();
                    System.out.println("用户 " + senderId + " 发送单聊消息给 " + targetId);
                    ctx.writeAndFlush(new TextWebSocketFrame(objectMapper.writeValueAsString(new Response<>("私发消息已经发送"))));
                    //持久化
                    SingleMessage singleMessage = new SingleMessage(objectMapper.convertValue(data.getTimestamp(), String.class),singleMsgDTO);
                    singleMessageService.persistMsg(singleMessage);
                }
                break;}
            case "HISTORIES_MESSAGE":
                System.out.println("HISTORIES_MESSAGE");
                HistoryMsgDTO historyMsgDTO = objectMapper.convertValue(data.getPayload(), HistoryMsgDTO.class);

                int senderId = historyMsgDTO.getSenderId();
                int targetId   = historyMsgDTO.getTargetId();
                singleMessageService.markMessagesAsRead(targetId, senderId);

                List<HistoryMsgVO> historiesMsg = singleMessageService.getHistoriesMsg(historyMsgDTO);
                ctx.writeAndFlush(new TextWebSocketFrame(objectMapper.writeValueAsString(new Response<>("HISTORIES_MESSAGE_ACK",historiesMsg))));

                Map<Integer,Integer> c = singleMessageService.getUnreadCounts(senderId);
                ctx.writeAndFlush(new TextWebSocketFrame(
                        objectMapper.writeValueAsString(new Response<>("UNREAD_COUNTS", c))
                ));

                break;

            //room chat
            case "CREATE_ROOM":
//                System.out.println("JOIN_ROOM");
//                //获取或创建ChannelGroup (创建群内聊的时候顺便加入)
//                CreateRoomDTO createRoomDTO = objectMapper.convertValue(data.getPayload(), CreateRoomDTO.class);
//                ChannelGroup channelGroup = roomChannelGroupMap.computeIfAbsent(createRoomDTO.getRoomId(), k ->
//                        // 3. 必须使用DefaultChannelGroup并指定EventExecutor
//                        new DefaultChannelGroup(GlobalEventExecutor.INSTANCE)
//                );
                //todo 鉴权实现 创建与加入 持久化
                roomChannelGroupMap.computeIfAbsent("1", k ->
                        new DefaultChannelGroup(GlobalEventExecutor.INSTANCE)
                );
                break;
            case "JOIN_ROOM":{
                //todo 持久化
                System.out.println("JOIN_ROOM");
                //获取或创建ChannelGroup (创建群内聊的时候顺便加入)
                JoinRoomDTO joinRoomDTO = objectMapper.convertValue(data.getPayload(), JoinRoomDTO.class);
                ChannelGroup channelGroup = roomChannelGroupMap.computeIfAbsent(joinRoomDTO.getRoomId(), k ->
                        // 3. 必须使用DefaultChannelGroup并指定EventExecutor
                        new DefaultChannelGroup(GlobalEventExecutor.INSTANCE)
                );
                //筛选加入
                ByteBuf responseBuf = ByteBufAllocator.DEFAULT.heapBuffer();
                try {
                    if (channelGroup.add(ctx.channel())) {
                        String json = objectMapper.writeValueAsString(new Response<>("已加入房间"));
                        responseBuf.writeBytes(json.getBytes(StandardCharsets.UTF_8));
                        ctx.writeAndFlush(new TextWebSocketFrame(responseBuf.retain()));
                    } else {
                        //已经加入房间
                        String errorJson = "{\"warning\": \"加入房间\"}";
                        responseBuf.writeBytes(errorJson.getBytes(StandardCharsets.UTF_8));
                        ctx.writeAndFlush(new TextWebSocketFrame(responseBuf.retain()));
                    }
                } catch (JsonProcessingException e) {
                    responseBuf.release();
//                    ctx.close();
                    e.printStackTrace();
                } finally {
                    responseBuf.release();
                }
                break;}
            case "LIST_ROOMS":
                //todo 查询
                //这里有ROOM的list
                break;
            case "ROOM_MESSAGE":{
                System.out.println(data.getPayload());
                RoomMsgDTO roomMsgDTO = objectMapper.convertValue(data.getPayload(), RoomMsgDTO.class);
                System.out.println(roomMsgDTO);
                //todo time????
//                roomMessage.setTime(LocalDateTime.now());
//                RoomMsgDTO(roomId=1, senderId=1, content=asdason)

                String roomId = String.valueOf(roomMsgDTO.getRoomId());

                ChannelGroup roomGroup = roomChannelGroupMap.get(roomId); // 查找房间 ChannelGroup

                ByteBuf broadcastBuf = ByteBufAllocator.DEFAULT.heapBuffer();
                ByteBuf ackBuf = ByteBufAllocator.DEFAULT.heapBuffer();
                try {
                    if (roomGroup != null) {

                        // 构建要发送的消息 ResponseData (类型如 "CHAT_MESSAGE"，payload 包含发送者、内容、时间、房间ID等)
                        // ResponseData<ChatMessage> chatMessage = buildChatMessageResponse(senderUserIdForRoom, roomId, content, "room"); // 示例构建方法
                        // String jsonMessage = mapper.writeValueAsString(chatMessage);

                        //这个信息包含更多信息就好了 新鞋两个类 也就是 构建要发送的消息
                        //todo:we need time;a vo is better
                        Response<RoomMsgDTO> roomMessageResponse = new Response<>("ROOM_MESSAGE_ACK", roomMsgDTO); // content 是消息内容
//                         String jsonMessage = objectMapper.writeValueAsString(roomMessageResponse);
                        broadcastBuf.writeBytes(objectMapper.writeValueAsBytes(roomMessageResponse));
                        TextWebSocketFrame messageFrame = new TextWebSocketFrame(broadcastBuf.retain());

                        // 向房间内的所有 Channel 广播消息，通常排除发送者自己的 Channel
                        Channel senderChannel = ctx.channel(); // 获取发送者 Channel
                        roomGroup.writeAndFlush(messageFrame, channel -> {
                            // 排除发送者自己的 Channel
                            return channel != senderChannel;
                        });
                        System.out.println("用户 " + "系统" + " 发送房间消息到 " + roomId);
                        // 确认响应
                        ackBuf.writeBytes(objectMapper.writeValueAsBytes(new Response<>("群发消息已经发送")));
                        ctx.writeAndFlush(new TextWebSocketFrame(ackBuf.retain()));
                        //持久化
                        System.out.println(roomMsgDTO.getSenderId());
                       //  roomMessageService.sendMessage(roomMsgDTO.getRoomId(), roomMsgDTO.getSenderId(), roomMsgDTO.getContent());
                    } else {
                        // 房间不存在
                        System.out.println("房间不存在");
                        // String errorJson = objectMapper.writeValueAsString(new Response<>("房间不存在"));
                        ackBuf.writeBytes(objectMapper.writeValueAsBytes(new Response<>("房间不存在")));
                        ctx.writeAndFlush(new TextWebSocketFrame(ackBuf.retain()));
                    }
                } catch (JsonProcessingException e) {
                    ackBuf.release();
                    broadcastBuf.release();
//                    ctx.close();
                    e.printStackTrace();
                } finally {
                    ackBuf.release();
                    broadcastBuf.release();
                }
                break;}
            case "FILE_DOWNLOAD":{
                // 构建文件路径（文件直接放在D盘下）
                String baseDir = "/home/molidis/Desktop/";
                SingleMsgDTO singleMsgDTO = objectMapper.convertValue(data.getPayload(), SingleMsgDTO.class);
                String fileName= singleMsgDTO.getContent();
                // 验证文件名安全性（防止路径遍历）
                if (!isValidFileName(fileName)) {
                    System.out.println("文件名不安全");
                    break;
                }
                try {
                    File file = new File(baseDir + fileName);
                    if (!file.exists() || !file.isFile()) {
                        System.out.println("文件不存在或不是文件");
                        break;
                    }
                    // 发送文件信息
                    String fileInfo = String.format("{\"responseMsg\":\"file_meta\",\"fileName\":\"%s\",\"fileSize\":%d}", file.getName(), file.length());
                    ctx.writeAndFlush(new TextWebSocketFrame(Unpooled.copiedBuffer(fileInfo, StandardCharsets.UTF_8)));
                    // 发送文件内容
                    RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
                    DefaultFileRegion region = new DefaultFileRegion(randomAccessFile.getChannel(), 0, file.length());
                    ctx.writeAndFlush(region).addListener((ChannelFutureListener)future -> {
                        if (future.isSuccess()) {
                            System.out.println("文件发送成功");
                        } else {
                            System.out.println("文件发送失败");
                        }
                        randomAccessFile.close();
                    });
                } catch (Exception e) {
                    e.printStackTrace();
                }
                break;}
            case "GET_UNREAD_COUNTS": {
                UnreadCountRequestDTO req = objectMapper.convertValue(data.getPayload(), UnreadCountRequestDTO.class);
                Map<Integer,Integer> counts2 = singleMessageService.getUnreadCounts(req.getUserId());
                ctx.writeAndFlush(new TextWebSocketFrame(
                        objectMapper.writeValueAsString(new Response<>("UNREAD_COUNTS", counts2))
                ));
                break;
            }
            case "CLEAR_CHAT_HISTORY":{
                System.out.println("接收到清空聊天记录的信息");
//                ClearHistoryDTO clearHistoryDTO = objectMapper.convertValue(data.getPayload(), ClearHistoryDTO.class);
//                singleMessageService.clearChatHistory(clearHistoryDTO);
//                ctx.writeAndFlush(new TextWebSocketFrame(objectMapper.writeValueAsString(new Response<>("success"))));
            }
            default:
                break;
        }
    }

    //处理新增客户端
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        // 新客户端连接时的操作
        System.out.println(ctx+" Connected");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        //ctx.close();
    }

    //处理客户端断开连接
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) {
        // 客户端断开时的操作
        Channel incoming = ctx.channel();
        System.out.println(incoming.remoteAddress() + " has left");

        // --- 在此处添加：从 userChannelMap 中移除断开连接的用户 ---
        // 使用 getUserIdByChannel 方法查找该 Channel 对应的用户ID
        String userId = getUserIdByChannel(incoming);
        if (userId != null) {
            // 如果找到了用户ID，说明该用户已登录，从 Map 中移除
            userChannelMap.remove(userId);
            System.out.println("用户 " + userId + " 断开连接，已从 userChannelMap 移除");

            broadcastOnlineUsers();

        } else {
            System.out.println("一个未登录的客户端断开连接");
        }

    }

    // *** 辅助方法：根据 Channel 查找用户ID ***
    private String getUserIdByChannel(Channel channel) {
        // 遍历 userChannelMap 的所有条目 (用户ID -> Channel)
        for (Map.Entry<String, Channel> entry : userChannelMap.entrySet()) {
            // 比较当前条目的 Value (Channel) 是否等于我们要查找的 Channel
            if (entry.getValue() == channel) {
                // 如果找到了，返回这个条目的 Key (用户ID)
                return entry.getKey();
            }
        }
        // 如果遍历完 Map 都没有找到对应的 Channel，说明这个 Channel 没有关联到已登录的用户
        return null; // 未找到对应用户
    }

    private List<Map<String,String>> getOnlineUserInfos() {
        return userChannelMap.keySet().stream()
                .map(userId -> {
                    // 从数据库或者内存里根据 userId 拿到用户名
                    String username = userMapper.getUsernameById(userId);
                    Map<String,String> info = new HashMap<>();
                    info.put("id", userId);
                    info.put("username", username);
                    return info;
                })
                .collect(Collectors.toList());
    }

    private void broadcastOnlineUsers() {
        List<Map<String, String>> onlineUserInfos = userChannelMap.keySet().stream()
                .map(userId -> {
                    String username = userMapper.getUsernameById(userId);
                    Map<String, String> userInfo = new HashMap<>();
                    userInfo.put("id", userId);
                    userInfo.put("username", username);
                    return userInfo;
                })
                .collect(Collectors.toList());

        //System.out.println("[DEBUG] Broadcasting online users: " + onlineUserInfos);

        try {
            byte[] msg = objectMapper.writeValueAsBytes(new Response<>("ONLINE_USERS", onlineUserInfos));
            for (Channel channel : userChannelMap.values()) {
                ByteBuf buffer = ByteBufAllocator.DEFAULT.heapBuffer(msg.length);
                try {
                    buffer.writeBytes(msg);
                    //System.out.println("[DEBUG] Sending online user list to channel: " + channel.remoteAddress());
                    channel.writeAndFlush(new TextWebSocketFrame(buffer.retain()));
                } finally {
                    buffer.release();
                }
            }
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }

    private void sendNotificationToUser(String userId, String message) {
        Channel userChannel = userChannelMap.get(userId);
        if (userChannel != null && userChannel.isActive()) {
            try {
                String notificationJson = objectMapper.writeValueAsString(
                        new Response<>("NOTIFICATION", message)
                );
                userChannel.writeAndFlush(new TextWebSocketFrame(notificationJson));
                System.out.println("[DEBUG] 发送通知给用户 " + userId + " : " + message);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("[DEBUG] 用户 " + userId + " 不在线，无法发送通知");
        }
    }

    private boolean isValidFileName(String fileName) {
        // 过滤路径遍历和特殊字符
        return fileName.matches("^[a-zA-Z0-9_\\-]+\\.[a-zA-Z0-9]+$");
    }
}
